﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Threading;
using System.Security.Cryptography;
using System.Windows.Threading;
using System.Windows;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;

namespace WpfImageDuplicates
{
    public static class FileImageOperators 
    {
        public static ThreadSafeQueue<FileImage> _loadQueue;

        public static double Similarity(this IFileImage f1, IFileImage f2)
        {
            Bitmap bm1 = f1.Bitmap;
            Bitmap bm2 = f2.Bitmap;
            double totalDiff = 0D;

            var bits1 = bm1.LockBits(new Rectangle(0, 0, bm1.Width, bm1.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            try
            {
                var bits2 = bm2.LockBits(new Rectangle(0, 0, bm2.Width, bm2.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                try
                {
                    var bits1Stride = bits1.Stride;
                    var bits2Stride = bits2.Stride;

                    int minStride = bits1Stride < bits2Stride ? bits1Stride : bits2Stride;
                    int minHeight = bits1.Height < bits2.Height ? bits1.Height : bits2.Height;
                    int toGo1 = bits1Stride - minStride;
                    int toGo2 = bits2Stride - minStride;

                    unsafe
                    {
                        void* pv1 = bits1.Scan0.ToPointer();
                        void* pv2 = bits2.Scan0.ToPointer();
                        byte* pb1 = (byte*)pv1;
                        byte* pb2 = (byte*)pv2;

                        for (int y = 0; y < minHeight; y++)
                        {
                            for (int n = 0; n < minStride; n++)
                            {
                                byte b1 = *pb1;
                                byte b2 = *pb2;
                                pb1++;
                                pb2++;

                                double d1 = (double)b1;
                                double d2 = (double)b2;

                                double diff = Math.Abs(d1 - d2);
                                totalDiff += diff;
                            }
                            pb1 += toGo1;
                            pb2 += toGo2;
                        }
                    }
                }
                finally
                {
                    bm2.UnlockBits(bits2);
                }
            }
            finally
            {
                bm1.UnlockBits(bits1);
            }
            return totalDiff;
        }

        public static List<Pair<IFileImage, IFileImage>> FindDuplicates(this FileImageCollection collection)
        {
            List<Pair<IFileImage, IFileImage>> dupes = new List<Pair<IFileImage, IFileImage>>();
            List<Pair<IFileImage, IFileImage>> dupesFirstPass = new List<Pair<IFileImage, IFileImage>>();
            List<IFileImage> originals = new List<IFileImage>(collection);

            Dictionary<IFileImage, List<IFileImage>> filesSameSize = new Dictionary<IFileImage, List<IFileImage>>();

            for (int n = originals.Count - 1; n >= 0; n--)
            {
                IFileImage original = collection[n];

                for (int i = 0; i < n; i++)
                {
                    IFileImage compare = collection[i];

                    if (original == compare)
                        continue;

                    if (original.FileSize == compare.FileSize)
                    {
                        List<IFileImage> list;
                        if (!filesSameSize.TryGetValue(original, out list))
                        {
                            list = new List<IFileImage>();
                            filesSameSize.Add(original, list);
                        }
                        if (!list.Contains(compare) && !list.Contains(original) && !filesSameSize.ContainsKey(compare))
                        {
                            list.Add(compare);
                        }
                    }
                }
            }

            foreach (var kvp in filesSameSize)
            {
                IFileImage original = kvp.Key;
                List<IFileImage> matches = kvp.Value;
                string checkSum = original.MD5Hash;
                original.ReleaseFile(true);

                for (int i = 0; i < matches.Count; i++)
                {
                    IFileImage compare = matches[i];
                    if (original != compare)
                    {
                        string otherChecksum = compare.MD5Hash;
                        compare.ReleaseFile(true);

                        if (checkSum.Equals(otherChecksum, StringComparison.Ordinal))
                        {
                            dupesFirstPass.Add(new Pair<IFileImage, IFileImage>(original, compare));
                        }
                    }
                }
            }

            Dictionary<IFileImage, IFileImage> alreadyFound = new Dictionary<IFileImage, IFileImage>();

            foreach (var v in dupesFirstPass)
            {
                IFileImage f1 = v.obj1;
                IFileImage f2 = v.obj2;

                if (alreadyFound.ContainsKey(f1) || alreadyFound.ContainsKey(f2))
                    continue;

                alreadyFound.Add(f1, f1);
                alreadyFound.Add(f2, f2);
                dupes.Add(v);
            }

            return dupes;
        }

    }
}
